use std::collections::HashMap;
use std::fs;
use std::sync::OnceLock;

static PINYIN_MAP: OnceLock<HashMap<char, String>> = OnceLock::new();
static WADE_GILES_MAP: OnceLock<HashMap<&'static str, &'static str>> = OnceLock::new();

pub fn converter(input: &str) -> String {
    let pinyin_map = PINYIN_MAP.get_or_init(load_pinyin_map);
    let wade_giles_map = WADE_GILES_MAP.get_or_init(init_wade_giles_map);

    input
        .chars()
        .enumerate()
        .filter_map(|(i, c)| {
            pinyin_map.get(&c).map(|pinyin| {
                let wade = to_wade_giles(pinyin, wade_giles_map);
                if i == 0 {
                    capitalize_first(&remove_tone(&wade))
                } else {
                    remove_tone(&wade).to_lowercase()
                }
            })
        })
        .collect::<Vec<_>>()
        .join(" ")
}
fn init_wade_giles_map() -> HashMap<&'static str, &'static str> {
    let mut map = HashMap::new();

    // Initials
    map.extend([
        ("b", "p"),
        ("p", "p'"),
        ("m", "m"),
        ("f", "f"),
        ("d", "t"),
        ("t", "t'"),
        ("n", "n"),
        ("l", "l"),
        ("g", "k"),
        ("k", "k'"),
        ("h", "h"),
        ("j", "ch"),
        ("q", "ch'"),
        ("x", "hs"),
        ("zh", "ch"),
        ("ch", "ch'"),
        ("sh", "sh"),
        ("r", "j"),
        ("z", "ts"),
        ("c", "ts'"),
        ("s", "s"),
        ("si", "ssu"),
    ]);

    // Finals
    map.extend([
        ("a", "a"),
        ("o", "o"),
        ("e", "o"),
        ("ai", "ai"),
        ("ei", "ei"),
        ("ao", "ao"),
        ("ou", "ou"),
        ("an", "an"),
        ("en", "en"),
        ("ang", "ang"),
        ("eng", "eng"),
        ("ong", "ung"),
        ("er", "erh"),
        ("i", "i"),
        ("ia", "ia"),
        ("iao", "iao"),
        ("ie", "ieh"),
        ("iu", "iu"),
        ("ian", "ien"),
        ("in", "in"),
        ("iang", "iang"),
        ("ing", "ing"),
        ("u", "u"),
        ("ua", "ua"),
        ("uo", "o"),
        ("uai", "uai"),
        ("ui", "uei"),
        ("un", "un"),
        ("uan", "uan"),
        ("uang", "uang"),
        ("uo", "uo"),
    ]);

    map
}

fn capitalize_first(s: &str) -> String {
    let mut chars = s.chars();
    match chars.next() {
        None => String::new(),
        Some(first) => first.to_uppercase().chain(chars).collect(),
    }
}

fn remove_tone(s: &str) -> String {
    s.chars().filter(|c| !c.is_ascii_digit()).collect()
}

fn to_wade_giles(pinyin: &str, wade_map: &HashMap<&str, &str>) -> String {
    let mut result = String::new();
    let pinyin = extract_pinyin_without_tone(pinyin);

    // TODO: idk why. That's weird.
    if pinyin == "si" {
        return "ssu".to_string();
    }

    let chars: Vec<char> = pinyin.chars().collect();
    let mut i = 0;

    while i < chars.len() {
        i += match_longest_substring(&chars[i..], wade_map, &mut result);
    }

    result
}

fn extract_pinyin_without_tone(pinyin: &str) -> String {
    pinyin.chars().filter(|&c| !c.is_ascii_digit()).collect()
}

fn match_longest_substring(
    chars: &[char],
    wade_map: &HashMap<&str, &str>,
    result: &mut String,
) -> usize {
    for len in (2..=4).rev() {
        if len <= chars.len() {
            let substr: String = chars[..len].iter().collect();
            if let Some(&wade) = wade_map.get(substr.as_str()) {
                result.push_str(wade);
                return len;
            }
        }
    }

    let single = chars[0].to_string();
    if let Some(&wade) = wade_map.get(single.as_str()) {
        result.push_str(wade);
    } else {
        result.push(chars[0]);
    }
    1
}

fn load_pinyin_map() -> HashMap<char, String> {
    let content =
        fs::read_to_string("src/pinyin_map.json").expect("Failed to read dictionary file");

    parse_pinyin_map_content(&content)
}

fn parse_pinyin_map_content(content: &str) -> HashMap<char, String> {
    content
        .trim()
        .trim_matches('{')
        .trim_matches('}')
        .split(',')
        .filter_map(|entry| {
            let entry = entry.trim();
            if entry.is_empty() {
                return None;
            }

            let parts: Vec<&str> = entry.split(':').collect();
            if parts.len() != 2 {
                return None;
            }

            let char_str = parts[0].trim().trim_matches('"').trim_matches('\\');
            let pinyin = parts[1].trim().trim_matches('"').trim_matches('\\');

            char_str.chars().next().map(|c| (c, pinyin.to_string()))
        })
        .collect()
}
